[iOS] 複数のUIWindowの挙動を確認する
1 はじめに
UIWindowとは、特別なUIViewでありビュー階層のルートとなるものです。
通常のアプリ作成では、実装の対象は、このウィンドウに載せられたUIViewControllerが主であり、ウインドウ自体は、テンプレート任せとなっているのであまり意識されていないと思います。
テンプレートから生成されるコードでは、AppDelegateクラスに、次のような1つのUIWindowを保持するプロパティが設定されています。
#import <UIKit/UIKit.h> @interface AppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @end
しかし、このUIWindowは、アプリに1つではありません。 下記のコードで、アプリのウインドウを全て列挙することができます。上のwindowプロパティは、このうちの1つだったという事です。
NSArray<UIWindow*> *windows = [UIApplication sharedApplication].windows;
今回は、この複数のUIWindowのについて確かめて見ました。 なおこの情報は、iOSのバージョンによって違いますので、本記事はiOS9.3での結果である事を予めご了承ください。
2 試験準備
まずは、UIApplicationが持つUIWindowの数と、それぞれの名前やプロパティを出力するメソッドをAppDelegate.mに作成します。
@implementation AppDelegate // ログ出力 - (void)log:(NSString *)title{ NSLog(@"[%@]",title); NSArray<UIWindow*> *windows = [UIApplication sharedApplication].windows; NSLog(@"Application.windows.count = %lu",(unsigned long)windows.count); for( UIWindow *window in windows){ NSLog(@"%@ keywindow = %d hidden=%d lebel=%f",window.class.description,window.keyWindow, window.hidden,window.windowLevel); } }
上記は、次のような出力を得られます。
[TITLE] <= ログ用のタグ Application.windows.count = 2 <= 現在windowsに2つのUIWindowがある UIWindow keywindow = 1 hidden=0 lebel=0.000000 <= 1つ目は、UIWindow レベルは0 UITextEffectsWindow keywindow = 0 hidden=0 lebel=10.000000 <= 2つ目のUITextEffectsWindow レベルは10
そして、UIWindowが表示された時と、非表示に成った時のNotificationでこれをコールするようにします。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeVisible:) name:UIWindowDidBecomeVisibleNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeHidden:) name:UIWindowDidBecomeHiddenNotification object:nil]; return YES; } // UIWindowが表示された時 -(void)windowDidBecomeVisible:(NSNotification*)notification { [self log:@"Visible"];// ログ出力 } // UIWindowが非表示された時 -(void)windowDidBecomeHidden:(NSNotification*)notification { [self log:@"Hidden"];// ログ出力 }
続いて操作画面です。
各UIは、それぞれ次の動作を行います。
①アラートを表示
②TextFieldを置いただけ
③キーボードを非表示にするコード
④ログ出力
// ③ hideKeyboard - (IBAction)tapHideKeyboard:(id)sender { [self.view endEditing:YES];//キーボード非表示 } // ④ Comfirmのボタンを押すと、ログを出力する - (IBAction)tapComfirmButton:(id)sender { AppDelegate *application = (AppDelegate*)[[UIApplication sharedApplication] delegate]; [application log:@"Confirm"]; }
3 動作確認
(1) 起動時
アプリを起動した時点で、UIWindowが一つ表示されます。レベルは0です。
[Visible] Application.windows.count = 1 UIWindow keywindow = 0 hidden=0 lebel=0.000000
その後、すぐに確認すると、表示時には、keywindow=FALSEだったのが、すぐにTRUEに変化しています。
[Confirm] Application.windows.count = 1 UIWindow keywindow = 1 hidden=0 lebel=0.000000
(2) アラート表示
アラートを表示すると、UITextEffectsWindowというウインドウが追加されました。レベルが10なので、これが最上位になっているようです。
[Visible] Application.windows.count = 2 UIWindow keywindow = 1 hidden=0 lebel=0.000000 UITextEffectsWindow keywindow = 0 hidden=0 lebel=10.000000
(3) アラートをクローズ
アラートをクローズしても、UIWindowsのNotificationは発生しません。(表示・非表示の変化はない)。 これは、Confirmボタンで確認しても変化がないことが確認できました。
[Confirm] Application.windows.count = 2 UIWindow keywindow = 1 hidden=0 lebel=0.000000 UITextEffectsWindow keywindow = 0 hidden=0 lebel=10.000000
(4) TextFieldにフォーカスを与える(キーボード表示)
キーボードが表示された時、新たにUIRemoteKeyboardWindowというウインドウが追加されました。レベルが10000000なので、今度は、これが最上位になっているのでのしょう。
[Visible] Application.windows.count = 3 UIWindow keywindow = 1 hidden=0 lebel=0.000000 UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000 UIRemoteKeyboardWindow keywindow = 0 hidden=0 lebel=10000000.000000
(5) キーボードを非表示にする
キーボードを非表示にすると、まず、UIRemoteKeyboardWindowのhidden属性がFALSEになり、その後、消えました。この時点では、レベルが一番高いUITextEffectsWindowが最上位になっていると考えられます。
[Hidden] Application.windows.count = 3 UIWindow keywindow = 1 hidden=0 lebel=0.000000 UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000 UIRemoteKeyboardWindow keywindow = 0 hidden=1 lebel=10000000.000000 [Confirm] Application.windows.count = 2 UIWindow keywindow = 1 hidden=0 lebel=0.000000 UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000
(6) キーボードの表示状態からアラートを出す
キーボードが表示されている時の状態は、下記の通りですが、
[Visible] Application.windows.count = 3 UIWindow keywindow = 1 hidden=0 lebel=0.000000 UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000 UIRemoteKeyboardWindow keywindow = 0 hidden=0 lebel=10000000.000000
この状態から、アラート表示すると、
UIRemoteKeyboardWindowのhiddenがTRUEに設定され、見えなくなります。 上の図を見ても、キーボードが消えているのを確認できます。
[Hidden] Application.windows.count = 3 UIWindow keywindow = 1 hidden=0 lebel=0.000000 UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000 UIRemoteKeyboardWindow keywindow = 0 hidden=1 lebel=10000000.000000
そして、アラートを閉じると、再びキーボードが出現して、UIRemoteKeyboardWindowのhiddenがFALSEに戻っています。
[Visible] Application.windows.count = 3 UIWindow keywindow = 1 hidden=0 lebel=0.000000 UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000 UIRemoteKeyboardWindow keywindow = 0 hidden=0 lebel=10000000.000000
4 最後に
最初に、書いた通り、この情報はiOSのバージョンによって変化します。試しに、先の動作試験をiOS8.2で実行すると、キーボード表示時に次のような結果となります。
[Confirm] Application.windows.count = 3 UIWindow keywindow = 1 hidden=0 lebel=0.000000 UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000 UITextEffectsWindow keywindow = 0 hidden=0 lebel=2100.000000
レベルの数値やクラス名などは、iOSによって違うため、そのまま決めうちで使用する事は出来ないことが分かります。
また、予想外だったのは、アラートを一度でも表示すると、追加されたUITextEffectsWindowが、最後まで残り続けることでした。
この結果を踏まえて次は、UIWindowsを自前で追加して、更に詳しく動作を追ってみたいと思います。
参考資料
UIKit Framework Reference UIWindow Class Reference
iOS開発におけるウィンドウ「UIWindow」の知られざる活用方法とは? #iOS
makeKeyWindow vs makeKeyAndVisible